package cz.semjobKB.knowledge.impl;

import cz.semjobKB.conllx.api.ISentence;
import cz.semjobKB.conllx.api.IWord;
import cz.semjobKB.conllx.node.EConstituent;
import cz.semjobKB.knowledge.api.IExtractedKnowledge;
import cz.semjobKB.knowledge.api.IGenerateKnowledgeResult;
import cz.semjobKB.knowledge.api.ITerminologyItem;
import cz.semjobKB.knowledge.api.ITripleItem;
import cz.semjobKB.search.api.IMatch;
import cz.semjobKB.search.api.ISearchMatches;
import cz.semjobKB.search.impl.SearchMatch;
import org.springframework.stereotype.Component;

import java.util.*;

@Component
/**
 *
 */
public class GenerateKnowledgeResult implements IGenerateKnowledgeResult {

	public IExtractedKnowledge generateResult(List<SearchMatch> searchResult, List<ISentence> sentences) {
		
		List<ITripleItem> tripleList = generateTripleList(searchResult);
		List<ITerminologyItem> terminologyList = generateTerminologyList(searchResult);
		Integer fullSentence = 0;
		Integer words = 0;
		for (ISentence sentence : sentences) {
			words += sentence.getWords().size();
			for (IWord word : sentence.getWords()) {
				if (word.getConstituent() == EConstituent.PREDICATE) {
					fullSentence++;
					break;
				}
			}
		}
		
		ExtractedKnowledge extractedKnowledge = new ExtractedKnowledge(tripleList, terminologyList, sentences.size(), fullSentence, words);
		
		return extractedKnowledge;		
	}

    /*
     * Converts matched triples from the search phase into knowledge base representation of triples
     */
	private List<ITripleItem> generateTripleList(List<SearchMatch> searchResult) {
		
		List<ITripleItem> tripleList = new ArrayList<ITripleItem>();
		
		for (ISearchMatches match : searchResult) {
			List<IMatch> predicates = match.getPredicateMatch();			
			List<IMatch> subjects = match.getSubjectMatch();
			List<IMatch> objects = match.getObjectMatch();			
			
			for (IMatch predicateMatch : predicates) {
				int predicateRootPos = predicateMatch.getPositionRootMatch();
				
				List<IMatch> subjectConnectedWithPred = new ArrayList<IMatch>();
				List<IMatch> objectConnectedWithPred = new ArrayList<IMatch>();
				
				for (IMatch subMatch : subjects) {
					if (subMatch.getPositionRootMatch() == predicateRootPos) {
						subjectConnectedWithPred.add(subMatch);
					}
				}
				
				for (IMatch objMatch : objects) {
					if (objMatch.getPositionRootMatch() == predicateRootPos) {
						objectConnectedWithPred.add(objMatch);
					}
				}
				
				/* Disable incomplete triples
				//Care if the triple is incomplete
				if (subjectConnectedWithPred.isEmpty()) {
					if (objectConnectedWithPred.isEmpty()) {
						continue;
					} else {
						for (IMatch onlyObj : objectConnectedWithPred) {
							tripleList.add(new TripleItem(null, predicateMatch.getMatchWord(), onlyObj.getMatchWord(), false));
						}
					}
				}
				
				//Only subject and predicate
				if (objectConnectedWithPred.isEmpty()) {
					for (IMatch subOnly : subjectConnectedWithPred) {
						tripleList.add(new TripleItem(subOnly.getMatchWord(), predicateMatch.getMatchWord(), null, false));
					}
				} else {
					//Complete triples
					for (IMatch sub : subjectConnectedWithPred) {
						for (IMatch obj : objectConnectedWithPred) {							
							tripleList.add(new TripleItem(sub.getMatchWord(), predicateMatch.getMatchWord(), obj.getMatchWord(), true));
						}
					}
				}*/
				if (!subjectConnectedWithPred.isEmpty() && !objectConnectedWithPred.isEmpty()) {
					for (IMatch sub : subjectConnectedWithPred) {
						for (IMatch obj : objectConnectedWithPred) {							
							tripleList.add(new TripleItem(sub.getMatchWord(), predicateMatch.getMatchWord(), obj.getMatchWord(), true));
						}
					}
				}
			}/// end processing all predicates
		} //end processing all searchMatches

		return tripleList;
	}

    /*
     * Converts matched terminology into knowledge representation of terminology
     */
	private List<ITerminologyItem> generateTerminologyList(List<SearchMatch> searchResult) {
		List<ITerminologyItem> terminologyList = new ArrayList<ITerminologyItem>();
		
		HashMap<String, Integer> lemmaFrequencyList = new HashMap<String,Integer>();
		HashMap<String, HashMap<String, Integer>> lemmaAndOrigText = new HashMap<String, HashMap<String, Integer>>();
		
		//Iterate trough all terminology matches
		for (ISearchMatches match : searchResult) {				
			//Iterate trough all word matches
			for (List<IWord> smallMatch : match.getTermMatch()) {				
				
				String originalText = "";
				String lemma = "";				
				
				//get lemma and text for the terminology item, it can be multiple word match
				for (IWord word : smallMatch) {					
					originalText += word.getText().toLowerCase() + " ";
					lemma += word.getLemma().toLowerCase() + " ";
				}
				
				if (!originalText.isEmpty()) {
					originalText = originalText.trim();
					lemma = lemma.trim();					
					
					if (lemmaFrequencyList.containsKey(lemma)) {
						lemmaFrequencyList.put(lemma, lemmaFrequencyList.get(lemma) + 1);
					} else {
						lemmaFrequencyList.put(lemma, 1);
					}
					
					if (lemmaAndOrigText.containsKey(lemma)) {
						HashMap<String, Integer> originalTextSet = lemmaAndOrigText.get(lemma);
						
						if(originalTextSet.containsKey(originalText)) {
							originalTextSet.put(originalText, originalTextSet.get(originalText) + 1);							
						} else {
							originalTextSet.put(originalText, 1);
						}						
					} else {						
						HashMap<String, Integer> originalTextSet = new HashMap<String, Integer>();
						originalTextSet.put(originalText, 1);
						lemmaAndOrigText.put(lemma, originalTextSet);
					}
				}				
			}
		}
		
		lemmaFrequencyList = sortByValue(lemmaFrequencyList);
						
		for (Map.Entry<String, Integer> entry : lemmaFrequencyList.entrySet()) {
			terminologyList.add(new TerminologyItem(lemmaAndOrigText.get(entry.getKey()), entry.getKey(), entry.getValue()));
		}
						
		return terminologyList;
	}

    /*
     * Sorts terminology list by its frequency
     */
	private <K, V extends Comparable<? super V>> HashMap<K, V> sortByValue(Map<K, V> map) 
	{
		@SuppressWarnings("unchecked")
		Map.Entry<K,V>[] array = map.entrySet().toArray(new Map.Entry[map.size()]);

		Arrays.sort(array, new Comparator<Map.Entry<K, V>>() 
				{
			public int compare(Map.Entry<K, V> e1, Map.Entry<K, V> e2) 
			{
				return e2.getValue().compareTo(e1.getValue());
			}
				});

		HashMap<K, V> result = new LinkedHashMap<K, V>();
		for (Map.Entry<K, V> entry : array)
			result.put(entry.getKey(), entry.getValue());

		return result;
	}
}
